//author Sylvain Bertrand <digital.ragnarok@gmail.com>
//Protected by GNU Affero GPL v3 with some exceptions.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <error.h>

//#include <linux/types.h>
#include <alga/pixel_fmts.h>
#include <alga/amd/dce6/dce6.h>
#include <alga/amd/si/ioctl.h>

#define e(m,...) error(0,0,m,##__VA_ARGS__)
#define o(m,...) printf(m "\n",##__VA_ARGS__)
#define unsignedl unsigned long
#define unsignedll unsigned long long

static void mode(char *str, uint16_t *h, uint16_t *v, uint8_t *r)
{
  *h=0;
  *v=0;
  *r=0;
  char *c=str;

  //hhhhx
  while(1){
    if(*c=='\0') return;
    if(*c=='x') break;
    ++c;
  }
  *c='\0';
  *h=(uint16_t)strtoul(str,NULL,10);
  *c='x';
 
  ++c;
  str=c;
  //vvvv@
  while(1){
    if(*c=='\0') return;
    if(*c=='@') break;
    ++c;
  }
  *c='\0';
  *v=(uint16_t)strtoul(str,NULL,10);
  *c='@';

  ++c;
  //rrr\0
  *r=(uint8_t)strtoul(c,NULL,10);
}

uint8_t alga_pixel_fmt(char *str)
{
  uint8_t fmt=1;
  while(1){
    if(fmt==ALGA_PIXEL_FMTS_N) return 0;
    if(!strncmp(str,alga_pixel_fmts_str[fmt],sizeof("ARGB2101010")))
      return fmt;
    ++fmt;
  }
}

int main(int argc, char *argv[])
{
  if(argc<4){
    e("missing arguments:idx(0->5) mode(1920x1080@60) pixel_fmt(ARGB8888)");
    goto err;
  }

  int f=open("/dev/si0", O_RDWR);
  if(f==-1){
    e("open failed");
    goto err;
  }

  //XXX:why did I do that?
  long p_sz=sysconf(_SC_PAGESIZE);
  if(p_sz==-1){
    e("get page size failed");
    goto err;
  }

  uint16_t h;
  uint16_t v;
  uint8_t r;
  mode(argv[2],&h,&v,&r);
  o("from '%s' horizontal=%u pixels, vertical=%u pixels, refresh rate=%u Hz",
                                                                 argv[2],h,v,r);

  uint8_t pixel_fmt=alga_pixel_fmt(argv[3]);

  struct si_mem mem;
  //Why did I do that? Anyway, for the GPU color blocks the frame buffer must
  //be aligned on 256 bytes boundary.
  mem.align=p_sz;
  uint64_t sz=2*(h*v*alga_pixel_fmts_sz[pixel_fmt]);
  mem.sz=sz;
  o("db_fb alloc sz=%016llx",(unsignedll)mem.sz);
  unsignedl req=_IOWR('d',SI_MEM_ALLOC,mem);
  r=ioctl(f,req,&mem);
  if(r==-1){
    e("alloc db_fb failed");
    goto err;
  }
  o("front_fb=0x%016llx back_fb=0x%016llx",(unsignedll)mem.gpu_addr,
                                                 (unsignedll)mem.gpu_addr+sz/2);

  struct si_dce_dp_set dp_set;
  memset(&dp_set,0,sizeof(dp_set));
  dp_set.idx=strtoul(argv[1],NULL,10);
  dp_set.primary=mem.gpu_addr;
  dp_set.secondary=mem.gpu_addr+sz/2;
  strncpy(&dp_set.mode[0],argv[2],sizeof(dp_set.mode));
  strncpy(&dp_set.pixel_fmt[0],argv[3],sizeof(dp_set.pixel_fmt));
  req=_IOR('d',SI_DCE_DP_SET,dp_set);
  r=ioctl(f,req,&dp_set);
  if(r==-1){
    e("dp set failed");
    goto err_free_fb;
  }
  return 0;

err_free_fb:
  req=_IOW('d',SI_MEM_FREE,mem.gpu_addr);
  r=ioctl(f,req,&mem.gpu_addr);
  if(r==-1) e("free vram fb failed (LEAK!)");

err:
  return EXIT_FAILURE;
}
